#!/usr/bin/perl

#
#  AVEC 2013 audio feature extraction script for openSMILE
#  (c) 2013 by Florian Eyben, TUM
#  (c) 2014-2019 audEERING.
#    All rights reserved, see file COPYING for license terms.
#

my $instance = $ARGV[0];
my $clean = $ARGV[1];

if ($#ARGV < 0) {
  print "Usage: $0 <instance id, e.g. 101_1_cut> [clean = 2 (recompute all files), = 1 (first instance, delete existing arff files to avoid appending to old file), = 0 delete nothing]\n";
  exit -1;
}

###

# path to SMILExtract binary
my $SE = "./SMILExtract";

# base path with Audio and Annotation folders
my $bp = "..";

# file names for files generated by the script
my $wav = "$bp/Audio/$instance\_audio.wav";
mkdir("vad");
my $vadcsv = "vad/$instance\_audio.csv";
mkdir("lld");
my $lld_htk = "lld/$instance\_audio.htk";
my $lld_arff = "lld/$instance\_audio.arff";
mkdir("functionals");
my $func_short = "functionals/$instance\_short.arff";
my $func_long = "functionals/$instance\_long.arff";
my $func_short_vad = "functionals/$instance\_short_vad_aligned.arff";
my $func_vad_seg = "functionals/$instance\_vad_seg.arff";
my $func_per_instance = "functionals/$instance\_per_instance.arff";


#### code ...

# generate WAV
if ($clean == 2) { 
  unlink($vadcsv);
}
unless (-e $wav) {
  print "Converting mp4 to wav for instance $instance ...\n";
  my $ret = system("mplayer -ao pcm:file=\"$wav\" -vo null \"$bp/Audio/$instance\_audio.mp4\" 2>/dev/null > /dev/null");
  print "DONE.\n";
}

# perform VAD to CSV
if ($clean == 2) { 
  unlink($vadcsv);
}
unless (-e $vadcsv) {
if (-e "tum_vad_2012.conf") {
  print "Performing VAD on audio stream $instance ...\n";
  my $cmd = "$SE -l 0 -C tum_vad_2012.conf -I \"$wav\" -csv \"$vadcsv\" -nologfile >/dev/null 2>/dev/null";
  my $ret = system($cmd);
  if ($ret) {
    print "  ERROR: Failed to run openSMILE! Instance: $instance.\n  Command: $cmd\n";
    exit -1;
  } else {
    print "DONE.\n";
  }
} else {
  print "WARN: Not performing VAD, vad config and data files not included in this package! Please contact the author for obtaining these.\n";
}
}

### read VAD from CSV
print "Loading VAD targets from $vadcsv.\n";
my @vad = ();
my $i = 0;
open(F, "<$vadcsv");
while(<F>) {
  chomp;
  my @x = split(/\s*;\s*/, $_);
  $vad[$i][0] = $x[0];
  $vad[$i][1] = $x[1];
  $i++;
}
close(F);
print "DONE. Loaded targets for ".($#vad+1)." frames.\n";

# find VAD segments
my @vad_segs = ();
my $voice = 0;
my $threshS = -0.2;
my $threshV = 0.2;
my $sil_num = 20;
my $voice_num = 10;

my $voice_cnt = 0;
my $sil_cnt = 0;
my $seg_nr = 0;
my $seg_start = 0;
for (my $i = 0; $i <= $#vad; $i++) {
  my $t = $vad[$i]->[0];
  my $v = $vad[$i]->[1];
  # hystereses:
  if ($voice) {
    if ($v < $threshS) {
      $sil_cnt++;
      if ($sil_cnt > $sil_num) { 
        $voice = 0;
        $vad_segs[$seg_nr]{"start"} = $seg_start - 1;
        $vad_segs[$seg_nr]{"end"} = $i - $sil_cnt + 3;
        $seg_nr++;
      }
    } else {
      $sil_cnt = 0;
    }
  } else {
    if ($v > $threshV) {
      $voice_cnt++;
      if ($voice_cnt > $voice_num) { 
        $seg_start = $i - $voice_cnt;
        $voice = 1; 
      }
    } else {
      $voice_cnt = 0;
    }
  }
}

## cut VAD segments for debugging purpose
print "Trimming VAD segments for debugging purpose..\n";
mkdir("vad/".$instance."_audio_voicesegs");
open(F, ">vad/$instance\_audio_voiceseg.sh");
print F "#!/bin/sh\n\n";
print F "mkdir $instance\_audio_voicesegs\n";
for (my $i = 0; $i <= $#vad_segs; $i++) {
  my $start = $vad_segs[$i]{"start"} * 0.01;
  my $end = $vad_segs[$i]{"end"} * 0.01;
  my $len = $end - $start;
  if ($clean == 2) { 
    unlink("vad/$instance\_audio_voicesegs/seg_$i.wav");
  }
  unless (-e "vad/$instance\_audio_voicesegs/seg_$i.wav") {
    my $cmd = "sox \"$wav\" \"vad/$instance\_audio_voicesegs/seg_$i.wav\" trim $start $len";
    #print "  seg_$i.wav  (start: $start, len: $len)\n";
    my $ret = system($cmd);
    if ($ret) {
      print "  ERROR: Failed to run sox! Instance: $instance.\n  Command: $cmd\n";
      exit -1;
    }
  }
  my $cmd = "sox \"../$wav\" \"$instance\_audio_voicesegs/seg_$i.wav\" trim $start $len";
  print F "$cmd\n";
}
print F "\n";
close(F);
print "DONE. (voice segmenter script in: vad/$instance\_audio_voiceseg.sh)\n";


# extract LLD to HTK format and ARFF
if ($clean == 2) {
  unlink("$lld_arff");
  unlink("$lld_htk");
}
unless (-e "$lld_arff") {
  print "Extracting LLD features ...\n";
  my $cmd = "$SE -C avec2013_lld_htk_and_arff.conf -I \"$wav\" -output_htk \"$lld_htk\" -output_arff \"$lld_arff\" -instname \"$instance\" -nologfile 2> /dev/null >/dev/null";
  my $ret = system($cmd);
  if ($ret) {
    unlink("$lld_arff");
    unlink("$lld_htk");
    print "  ERROR: Failed to run openSMILE! Instance: $instance.\n  Command: $cmd\n";
    exit -1;
  } else {
    print "DONE.\n";
  }
}

# extract functionals (3s) at rate 1s to ARFF with labels (A/V/Depri/VAD%)
if ($clean) {
  unlink("$func_short");
}
unless (-e $func_short) {
print "Extracting Functionals (short) ...\n";
my $cmd = "$SE -C avec2013_functionals.conf -I \"$wav\" -O \"$func_short\" -frameModeConf avec2013_functionals_frame_mode_short.conf -instname \"$instance\" -nologfile 2> /dev/null >/dev/null";
my $ret = system($cmd);
if ($ret) {
  print "  ERROR: Failed to run openSMILE! Instance: $instance.\n  Command: $cmd\n";
  unlink("$func_short");
  exit -1;
} else {
  print "DONE.\n";
}
}


# extract functionals long (20s) at rate 1s to ARFF with labels (A/V/Depri)
if ($clean) {
  unlink("$func_long");
}
unless (-e $func_long) {
print "Extracting Functionals (long) ...\n";
my $cmd = "$SE -C avec2013_functionals.conf -I \"$wav\" -O \"$func_long\" -frameModeConf avec2013_functionals_frame_mode_long.conf -instname \"$instance\" -nologfile 2>/dev/null >/dev/null";
my $ret = system($cmd);
if ($ret) {
  print "  ERROR: Failed to run openSMILE! Instance: $instance.\n  Command: $cmd\n";
  unlink("$func_long");
  exit -1;
} else {
  print "DONE.\n";
}
}

# extract functionals for whole segment to ARFF with labels (A/V/Depri)
if ($clean) {
  unlink("$func_per_instance");
}
unless (-e $func_per_instance) {
print "Extracting Functionals (per instance) ...\n";
my $cmd = "$SE -C avec2013_functionals.conf -I \"$wav\" -O \"$func_per_instance\" -frameModeConf avec2013_functionals_frame_mode.conf -instname \"$instance\" -nologfile 2>/dev/null >/dev/null";
my $ret = system($cmd);
if ($ret) {
  print "  ERROR: Failed to run openSMILE! Instance: $instance.\n  Command: $cmd\n";
  unlink("$func_per_instance");
  exit -1;
} else {
  print "DONE.\n";
}
}

## VAD segment functionals:
if ($clean) {
  unlink("$func_vad_seg");
}
# extract functionals of VAD segments to ARFF with labels (A/V/Depri)
unless (-e $func_vad_seg) {
print "Extracting functionals for VAD segments (full) ...\n";
for (my $i = 0; $i <= $#vad_segs; $i++) {
  my $start = $vad_segs[$i]{"start"} * 0.01;
  my $end = $vad_segs[$i]{"end"} * 0.01;
  my $cmd = "$SE -C avec2013_functionals.conf -frameTimeAdd \"$start\" -start \"$start\" -end \"$end\" -I \"$wav\" -O \"$func_vad_seg\" -frameModeConf avec2013_functionals_frame_mode.conf -instname \"$instance\_seg$i\" -nologfile 2>/dev/null >/dev/null";
  my $ret = system($cmd);
  if ($ret) {
    print "  ERROR: Failed to run openSMILE! Instance: $instance.\n  Command: $cmd\n";
    unlink("$func_vad_seg");
    exit -1;
  }
}
print "DONE.\n";
}

if ($clean) {
  unlink("$func_short_vad");
}
# extract functionals of VAD segments to ARFF with labels (A/V/Depri)
if (0) {
unless (-e $func_short_vad) {
print "Extracting functionals for VAD segments (short) ...\n";
for (my $i = 0; $i <= $#vad_segs; $i++) {
  my $start = $vad_segs[$i]{"start"} * 0.01;
  my $end = $vad_segs[$i]{"end"} * 0.01;
  my $cmd = "$SE -C avec2013_functionals.conf -frameTimeAdd \"$start\" -start \"$start\" -end \"$end\" -I \"$wav\" -O \"$func_short_vad\" -frameModeConf avec2013_functionals_frame_mode_short_with_last.conf -instname \"$instance\_seg$i\" -nologfile 2>/dev/null >/dev/null";
  my $ret = system($cmd);
  if ($ret) {
    print "  ERROR: Failed to run openSMILE! Instance: $instance.\n  Command: $cmd\n";
    unlink("$func_short_vad");
    exit -1;
  }
}
print "DONE.\n";
}
}

exit 0;

